/**
 * $Id: CodecRegistry.java,v 1.24 2010-09-15 11:57:07 gaudenz Exp $
 * Copyright (c) 2007, Gaudenz Alder
 */
package com.kg.rails.component.editor.io;

import com.kg.rails.component.editor.model.GraphModel.*;

import java.util.*;

/**
 * Singleton class that acts as a global registry for codecs. See
 * {@link Codec} for an example.
 */
public class CodecRegistry {

    /**
     * Maps from constructor names to codecs.
     */
    protected static Hashtable<String, ObjectCodec> codecs = new Hashtable<String, ObjectCodec>();

    /**
     * Maps from classnames to codecnames.
     */
    protected static Hashtable<String, String> aliases = new Hashtable<String, String>();

    /**
     * Holds the list of known packages. Packages are used to prefix short
     * class names (eg. Cell) in XML markup.
     */
    protected static List<String> packages = new ArrayList<String>();

    // Registers the known codecs and package names
    static {
        addPackage("com.mxgraph");
        addPackage("com.kg.rails.component.editor.util");
        addPackage("com.kg.rails.component.editor.model");
        addPackage("com.kg.rails.component.editor.view");
        addPackage("com.kg.rails.component.editor.model.custom");
        addPackage("java.lang");
        addPackage("java.util");

        register(new ObjectCodec(new ArrayList<Object>()));
        register(new ModelCodec());
        register(new CellCodec());
        register(new StylesheetCodec());

        register(new RootChangeCodec());
        register(new ChildChangeCodec());
        register(new TerminalChangeCodec());
        register(new GenericChangeCodec(new mxValueChange(), "value"));
        register(new GenericChangeCodec(new mxStyleChange(), "style"));
        register(new GenericChangeCodec(new mxGeometryChange(), "geometry"));
        register(new GenericChangeCodec(new mxCollapseChange(), "collapsed"));
        register(new GenericChangeCodec(new mxVisibleChange(), "visible"));
    }

    /**
     * Registers a new codec and associates the name of the template constructor
     * in the codec with the codec object. Automatically creates an alias if the
     * codename and the classname are not equal.
     */
    public static ObjectCodec register(ObjectCodec codec) {
        if (codec != null) {
            String name = codec.getName();
            codecs.put(name, codec);

            String classname = getName(codec.getTemplate());

            if (!classname.equals(name)) {
                addAlias(classname, name);
            }
        }

        return codec;
    }

    /**
     * Adds an alias for mapping a classname to a codecname.
     */
    public static void addAlias(String classname, String codecname) {
        aliases.put(classname, codecname);
    }

    /**
     * Returns a codec that handles the given object, which can be an object
     * instance or an XML node.
     *
     * @param name Java class name.
     */
    public static ObjectCodec getCodec(String name) {
        String tmp = aliases.get(name);

        if (tmp != null) {
            name = tmp;
        }

        ObjectCodec codec = codecs.get(name);

        // Registers a new default codec for the given name
        // if no codec has been previously defined.
        if (codec == null) {
            Object instance = getInstanceForName(name);

            if (instance != null) {
                try {
                    codec = new ObjectCodec(instance);
                    register(codec);
                } catch (Exception e) {
                    // ignore
                }
            }
        }

        return codec;
    }

    /**
     * Adds the given package name to the list of known package names.
     *
     * @param packagename Name of the package to be added.
     */
    public static void addPackage(String packagename) {
        packages.add(packagename);
    }

    /**
     * Creates and returns a new instance for the given class name.
     *
     * @param name Name of the class to be instantiated.
     * @return Returns a new instance of the given class.
     */
    public static Object getInstanceForName(String name) {
        Class<?> clazz = getClassForName(name);

        if (clazz != null) {
            if (clazz.isEnum()) {
                // For an enum, use the first constant as the default instance
                return clazz.getEnumConstants()[0];
            } else {
                try {
                    return clazz.newInstance();
                } catch (Exception e) {
                    // ignore
                }
            }
        }

        return null;
    }

    /**
     * Returns a class that corresponds to the given name.
     *
     * @param name
     * @return Returns the class for the given name.
     */
    public static Class<?> getClassForName(String name) {
        try {
            return Class.forName(name);
        } catch (Exception e) {
            // ignore
        }

        for (int i = 0; i < packages.size(); i++) {
            try {
                String s = packages.get(i);

                return Class.forName(s + "." + name);
            } catch (Exception e) {
                // ignore
            }
        }

        return null;
    }

    /**
     * Returns the name that identifies the codec associated
     * with the given instance..
     * <p/>
     * The I/O system uses unqualified classnames, eg. for a
     * <code>com.kg.rails.component.editor.model.Cell</code> this returns
     * <code>Cell</code>.
     *
     * @param instance Instance whose node name should be returned.
     * @return Returns a string that identifies the codec.
     */
    public static String getName(Object instance) {
        Class<? extends Object> type = instance.getClass();

        if (type.isArray() || Collection.class.isAssignableFrom(type)
                || Map.class.isAssignableFrom(type)) {
            return "Array";
        } else {
            if (packages.contains(type.getPackage().getName())) {
                return type.getSimpleName();
            } else {
                return type.getName();
            }
        }
    }

}
